package net.brucecooper.mindmap.client;

import gwt.canvas.client.Canvas;

import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.HasContextMenuHandlers;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;

public abstract class ScrollingCanvas extends ScrollPanel implements HasContextMenuHandlers {
    AbsolutePanel apan = new AbsolutePanel();
    Canvas[] canvases;
    
    FocusPanel focusPanel = new FocusPanel(apan);
    protected int clipX1 = -1;
    protected int clipX2 = -1;
    protected int clipY1 = -1;
    protected int clipY2 = -1;
    
    private static int MAX_CANVAS_HEIGHT = 10000;
    
    
    public ScrollingCanvas(int initWidth, int initHeight) {
        apan.setWidth(initWidth + "px");
        apan.setHeight(initHeight+"px");
        Logger.log("Creating scrolling canvas");

        setupCanvases(initWidth, initHeight);
        
        setHeight((initHeight+20)+"px");
        setWidth("200px");
        setWidget(focusPanel);

        addScrollHandler(new ScrollHandler() {
            public void onScroll(ScrollEvent event) {
//                Logger.log("Got scroll Event");
                int xLeft = getVisibleXLeft();
                int xRight = getVisibleXRight();
                if (xLeft != clipX1 || xRight != clipX2) {
                    for (int i = 0; i < canvases.length; i++) {
                        apan.setWidgetPosition(canvases[i], getVisibleXLeft(), i*MAX_CANVAS_HEIGHT);
                    }
                    repaint();
                }
            }
        });
        Window.addResizeHandler(new ResizeHandler() {
            public void onResize(ResizeEvent event) {
//                Logger.log("Window Resized... Repainting");
                repaint();
            }
        });
        
        
        
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                repaint();
            }
        });

    }
    
    private static int calcNumberofCanvases(int height) {
        return (height+ MAX_CANVAS_HEIGHT-1)/MAX_CANVAS_HEIGHT;
    }
    
    protected boolean isOverBackground(int x, int y) {
        return true;
    }
    
    private void setupCanvases(int initWidth, int initHeight) {
        int numCanvases = calcNumberofCanvases(initHeight);
        canvases = new Canvas[numCanvases];
        int y = 0;
        int yRemain = initHeight;
        int i = 0;
        while (yRemain > 0) {
            canvases[i] = new Canvas(initWidth, Math.min(MAX_CANVAS_HEIGHT, yRemain));
//            canvases[i].addMouseMoveHandler(mouseHandler);
            apan.add(canvases[i], 0, y);
            yRemain -= MAX_CANVAS_HEIGHT;
            y += MAX_CANVAS_HEIGHT;
            i++;
        }

    }

    public void setCanvasSize(int width, int height) {
        setHeight((height + 20) + "px");
        apan.setWidth(width + "px");
        apan.setHeight(height + "px");
        
        int canvasDiff = calcNumberofCanvases(height) - canvases.length;
        
        if (canvasDiff == 0) {
            // Check that the 
            int lastHeight = height % MAX_CANVAS_HEIGHT;
            if (lastHeight == 0 && height != 0) {
                lastHeight = MAX_CANVAS_HEIGHT;
            }
            canvases[canvases.length-1].setHeight(lastHeight);
        } else {
            // TODO this potentially re-creates some big objects.  It would be more efficcient to only create what is needed.
            apan.clear();
            setupCanvases(width, height);
        }
        
        repaint();
    }
    
    
    /**
     * As this uses pre-computed values for the clip region, this should only be used
     * from the paint method.
     * @param x
     * @param y
     * @param width
     * @param height
     * @return
     */
    protected boolean intersectsClip(int x, int y, int width, int height) {
        return ((x + width) > clipX1) && (x <= clipX2) && 
               ((y + height) > clipY1) && (y <= clipY2); 
    }
    
    protected int getVisibleXLeft() {
        return getHorizontalScrollPosition();

    }
    
    protected int getVisibleXRight() {
        return getVisibleXLeft() + getOffsetWidth();

    }
    
   
    protected void repaint() {
//        Logger.log("Painting scrolling canvas");
        int visibleWidth = getOffsetWidth();
//        int height = apan.getOffsetHeight();
        // Make it bigger if necessary, but not smaller.
        
        int remainingY = apan.getOffsetHeight();
        for (int i = 0; i < canvases.length; i++) {
            Canvas canvas = canvases[i];
            
            if (visibleWidth > canvas.getWidth()) {
                canvas.setWidth(visibleWidth);        
            }
            
            clipX1 = getVisibleXLeft();
            clipX2 = getVisibleXRight();
            clipY1 = i*MAX_CANVAS_HEIGHT;
            clipY2 = clipY1 + Math.min(remainingY, MAX_CANVAS_HEIGHT);
            canvas.setFillStyle("white");
            canvas.fillRect(0, 0, visibleWidth, canvas.getHeight());
//            canvas.setStrokeStyle("red");
//            canvas.strokeRect(0, 0, visibleWidth-1, canvas.getHeight()-1);
    
            canvas.save();
            canvas.translate(-clipX1, -MAX_CANVAS_HEIGHT*i);
            paint(canvas);
            canvas.restore();
            remainingY -= MAX_CANVAS_HEIGHT;
        }
    }
    
    protected abstract void paint(Canvas canvas);

    public void addMouseDownHandler(MouseDownHandler mouseEventRedirector) {
        focusPanel.addMouseDownHandler(mouseEventRedirector);
    }

    public void addMouseUpHandler(MouseUpHandler mouseEventRedirector) {
        focusPanel.addMouseUpHandler(mouseEventRedirector);
        
    }

    public HandlerRegistration addMouseMoveHandler(MouseMoveHandler mouseEventRedirector) {
        return focusPanel.addMouseMoveHandler(mouseEventRedirector);
    }

    public void addKeyPressHandler(KeyPressHandler keyPressHandler) {
        focusPanel.addKeyPressHandler(keyPressHandler);
    }

    public Canvas getLayoutCanvas() {
        return canvases[0];
    }

    public void setFocus(boolean b) {
        focusPanel.setFocus(b);
        
    }

    public void removeAbsolute(Widget editor) {
        apan.remove(editor);
    }

    public void addAbsolute(Widget editor, int x, int y) {
        apan.add(editor, x, y);
        
    }

    public int getRelativeX(MouseMoveEvent e) {
        return e.getRelativeX(apan.getElement());
    }
    public int getRelativeY(MouseMoveEvent e) {
        return e.getRelativeY(apan.getElement());
    }
    
    public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler) {
        return  addDomHandler(handler, ContextMenuEvent.getType());
    }
    
    public void setScrollPosition(int position) {
        new Throwable().printStackTrace();
        super.setScrollPosition(position);
    }

    
}
